﻿using MultiLang;
namespace Rsdn.Janus
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading;
    using System.Web;
    using System.Windows.Forms;

    using AutoSave;
    using BLToolkit.Data;
    using BLToolkit.Data.Linq;
    using Framework;
    using JanusLibAing.AutoSave;
    using JetBrains.Annotations;
    using Log;
    using Scintilla;
    using Shortcuts;
    using SmartApp;

    internal partial class MessageForm : JanusBaseForm, IPreviewSource
    {
        private static string _messagePreviewTemplate;
        private const int MaxSubjectLength = 64;

        #region Delegate & Events
        public delegate void SendMessageEventHandler(object sender, int messageId);

        public static event SendMessageEventHandler MessageSent;

        #endregion

        #region Editor Declarations
        private const int _level1Style = 1;
        private const int _level2Style = 10;
        private const int _level3Style = 11;
        private const int _quotaPrefixStyle = 2;
        private static readonly Regex _quotedLineRx = new Regex(
            @"(?m)^(?'prefix'\s*\S{0,5}?(?'level'>+))[^>]",           //MLHIDE
            RegexOptions.Compiled);

        private static readonly Regex _tagRx = new Regex(
            @"\[/?(?<tag>[^\[\]=]+)=*[^\]]*\]",                       //MLHIDE
            RegexOptions.Compiled);

        // TODO : генерировать автоматом из _knownSmiles и _knownTags
        private static readonly Regex _smileRx = new Regex(
            @":up:|:down:|:super:|:shuffle:" +                        //MLHIDE
            @"|:crash:|:maniac:|:user:|:wow:|:beer:|:team:|:no:|" +   //MLHIDE
            @":nopont:|:xz:|(?<!:):-?\)\)\)|(?<!:):-?\)\)|(?<!:):-?\)|" + //MLHIDE
            @"(?<!;|amp|gt|lt|quot);[-oO]?\)|(?<!:):-?\(|(?<!:):-[\\/]|:\?\?\?:"); //MLHIDE

        private static readonly HashSet<string> _knownSmiles =
            new HashSet<string>
				{
					":", // not smile, but for prevention of automatic replacement ':' by ':)' //MLHIDE
					":)",                                                            //MLHIDE
					":))",                                                           //MLHIDE
					":)))",                                                          //MLHIDE
					":(",                                                            //MLHIDE
					//";)", // this single smile, not started with ':', not used in autocomplete
					":-\\",                                                          //MLHIDE
					":)",                                                            //MLHIDE
					":up:",                                                          //MLHIDE
					":down:",                                                        //MLHIDE
					":super:",                                                       //MLHIDE
					":shuffle:",                                                     //MLHIDE
					":crash:",                                                       //MLHIDE
					":maniac:",                                                      //MLHIDE
					":user:",                                                        //MLHIDE
					":wow:",                                                         //MLHIDE
					":beer:",                                                        //MLHIDE
					":team:",                                                        //MLHIDE
					":no:",                                                          //MLHIDE
					":nopont:",                                                      //MLHIDE
					":xz:",                                                          //MLHIDE
					":???:?0" // scintilla use '?' as image marker, '?0' is never used //MLHIDE
				};

        private static readonly Dictionary<string, bool> _knownTags =
            new Dictionary<string, bool>
				{
					{ "b", true },                                                   //MLHIDE
					{ "i", true },                                                   //MLHIDE
					{ "c#", true },                                                  //MLHIDE
					{ "url", true },                                                 //MLHIDE
					{ "img", true },                                                 //MLHIDE
					{ "msil", true },                                                //MLHIDE
					{ "list", true },                                                //MLHIDE
					{ "*", false },                                                  //MLHIDE
					{ "midl", true },                                                //MLHIDE
					{ "asm", true },                                                 //MLHIDE
					{ "ccode", true },                                               //MLHIDE
					{ "code", true },                                                //MLHIDE
					{ "pascal", true },                                              //MLHIDE
					{ "vb", true },                                                  //MLHIDE
					{ "sql", true },                                                 //MLHIDE
					{ "java", true },                                                //MLHIDE
					{ "email", true },                                               //MLHIDE
					{ "msdn", true },                                                //MLHIDE
					{ "perl", true },                                                //MLHIDE
					{ "php", true },                                                 //MLHIDE
					{ "erlang", true },                                              //MLHIDE
					{ "haskell", true },                                             //MLHIDE
					{ "lisp", true },                                                //MLHIDE
					{ "ml", true },                                                  //MLHIDE
					{ "py", true },                                                  //MLHIDE
					{ "rb", true },                                                  //MLHIDE
					{ "prolog", true },                                              //MLHIDE
					{ "q", true },                                                   //MLHIDE
					{ "hr", false },                                                 //MLHIDE
					{ "article", false },                                            //MLHIDE
					{ "h1", true },                                                  //MLHIDE
					{ "h2", true },                                                  //MLHIDE
					{ "h3", true },                                                  //MLHIDE
					{ "h4", true },                                                  //MLHIDE
					{ "h5", true },                                                  //MLHIDE
					{ "h6", true },                                                  //MLHIDE
					{ "t", true },                                                   //MLHIDE
					{ "th", true },                                                  //MLHIDE
					{ "tr", true },                                                  //MLHIDE
					{ "td", true },                                                  //MLHIDE
					{ "xml", true }                                                  //MLHIDE
				};

        #endregion

        #region Declarations
        /// <summary>
        private readonly StripMenuGenerator _menuGenerator;
        /// </summary>
        /// <summary>
        /// DTO для данных формы
        /// </summary>
        private readonly MessageInfo _messageInfo;
        private readonly int _previewSourceNum;
        private readonly ServiceManager _serviceManager;
        private readonly IServiceProvider _provider;
        private readonly SmilesToolbarGenerator _tagsbarGenerator;
        private readonly StripMenuGenerator _toolbarGenerator;

        /// <summary>
        ///   Режим формы
        /// </summary>
        private MessageFormMode _formMode;

        private readonly string _editorTextIn;

        private bool _isModified;
        private QuoteAnalyzer _quoteAnalyzer;
        private SmilesToolbar _tagsBar;

        private readonly AutoSaver _autoSaver;
        private string _autoSaveFileName;
        private readonly DataItemMessageType _dataItemMessageType = new DataItemMessageType();
        private readonly DataItemForumId _dataItemForumId = new DataItemForumId();
        private readonly DataItemReplyId _dataItemReplyId = new DataItemReplyId();
        private readonly DataItemSubject _dataitemSubject = new DataItemSubject();
        private readonly DataItemMessageId _dataItemMessageId = new DataItemMessageId();
        private readonly DataItemMessageText _dataItemMessageText = new DataItemMessageText();
        private readonly DataItemEditor _dataItemEditor = new DataItemEditor();
        private bool _isAutoSaveModified = false;
        #endregion

        #region Constructor(s)

        /// <summary>
        /// Initializes a new instance of the <see cref="MessageForm"/> class.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="mode">The mode.</param>
        /// <param name="message">The message.</param>
        /// <param name="editorTextIn"></param>
        /// <param name="autoSaveFileName"></param>
        public MessageForm([NotNull] IServiceProvider provider, MessageFormMode mode, MessageInfo message, string editorTextIn, string autoSaveFileName)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");          //MLHIDE
            }

            _provider = provider;
            _serviceManager = new ServiceManager(provider);

            _messageInfo = message ?? new MessageInfo();

            // lock all mesages for send
            if (_messageInfo.ID > 0)
            {
                IOutboxManager outboxManager = _serviceManager.GetRequiredService<IOutboxManager>();
                outboxManager.LockMessage(_messageInfo.ID);
            }

            _autoSaveFileName = autoSaveFileName;

            _autoSaver = new AutoSaver(new AutoSaveEditorFactory(), _autoSaveFileName);
            _autoSaver.RootFolder = Config.Instance.EditorAutoSave.PathEditorAutoSave;
            _autoSaver.SaveInterval = Config.Instance.EditorAutoSave.PeriodEditorAutoSave;
            _autoSaver.OnSaveData += _autoSaver_OnSaveData;
            _autoSaver.OnError += _autoSaver_OnError;

            _autoSaver.AddItem(_dataItemMessageType);
            _autoSaver.AddItem(_dataItemMessageId);
            _autoSaver.AddItem(_dataItemForumId);
            _autoSaver.AddItem(_dataItemReplyId);
            _autoSaver.AddItem(_dataitemSubject);
            _autoSaver.AddItem(_dataItemMessageText);
            _autoSaver.AddItem(_dataItemEditor);

            InitializeComponent();

            _subjectTextBox.MaxLength = MaxSubjectLength;
            _labelSymbolsRest.Text = MaxSubjectLength.ToString();

            _formMode = mode;
            _editorTextIn = editorTextIn;
            _previewSourceNum = OutboxManager.RegisterPreviewSource(this);

            _serviceManager.Publish<IMessageEditorService>(new MessageEditorService(this));

            CustomInitializeComponent();

            _menuGenerator = new StripMenuGenerator(_serviceManager, _menuStrip, "MessageEditor.Menu"); //MLHIDE
            _toolbarGenerator = new StripMenuGenerator(_serviceManager, _toolStrip, "MessageEditor.Toolbar"); //MLHIDE
            _tagsbarGenerator = new SmilesToolbarGenerator(_serviceManager, "Forum.MessageEditor.TagsBar", _tagsBar); //MLHIDE
        }
        #endregion

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Form.FormClosing"/> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Windows.Forms.FormClosingEventArgs"/> that contains the event data.</param>
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            if (IsModified)
            {
                DialogResult dlgRes = MessageBox.Show(
                    this,
                    SR.MessageEditor.SavePromptRequired,
                    SR.MessageEditor.Message,
                    MessageBoxButtons.YesNoCancel,
                    MessageBoxIcon.Question);

                if (dlgRes == DialogResult.Cancel)
                {
                    e.Cancel = true;
                    return;
                }

                if (dlgRes == DialogResult.Yes)
                {
                    if (!_messageInfo.Hold)
                    {
                        dlgRes = MessageBox.Show(
                            this,
                            SR.MessageEditor.SendPromptRequired,
                            SR.MessageEditor.Message,
                            MessageBoxButtons.YesNo,
                            MessageBoxIcon.Question);

                        _messageInfo.Hold = dlgRes != DialogResult.Yes;
                    }

                    SaveMessage(true);
                }
                else
                {
                    DeleteAutoSavedVersion();
                }
                _autoSaver.OnSaveData -= _autoSaver_OnSaveData;
                _autoSaver.OnError -= _autoSaver_OnError;
            }

            // обновляем сообщение согласно исходным данным - why?
            //UpdateOutboxMessage(_messageInfo);
            //_serviceManager.GetRequiredService<IOutboxManager>().NewMessages.Refresh();
            //_serviceManager.GetRequiredService<IDraftboxManager>().NewMessages.Refresh();
        }


        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Form.Closed"/> event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.EventArgs"/> that contains the event data.</param>
        protected override void OnClosed(EventArgs e)
        {
            _autoSaver.OnSaveData -= _autoSaver_OnSaveData;

            OutboxManager.UnregisterPreviewSource(_previewSourceNum);

            Config.Instance.MessageFormBounds.Bounds =
                WindowState == FormWindowState.Normal ? Bounds : RestoreBounds;

            Config.Instance.MessageFormBounds.Maximized =
                WindowState == FormWindowState.Maximized;

            Config.Instance.ShowMessageFormTagBar = _tagsBar.Visible;

            // unlock all messages for send
            if (_messageInfo.ID > 0)
            {
                IOutboxManager outboxManager = _serviceManager.GetRequiredService<IOutboxManager>();
                outboxManager.ReleaseMessage(_messageInfo.ID);
            }
        }


        /// <summary>
        /// Handles the OnError event of the _autoSaver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.IO.ErrorEventArgs"/> instance containing the event data.</param>
        void _autoSaver_OnError(object sender, ErrorEventArgs e)
        {
            _provider.LogError(e.GetException().Message);
        }


        /// <summary>
        /// Handles the OnSaveData event of the _autoSaver control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="JanusLibAing.AutoSave.AutoSaveEventArgs"/> instance containing the event data.</param>
        void _autoSaver_OnSaveData(object sender, AutoSaveEventArgs e)
        {
            if (_isAutoSaveModified)
            {
                SaveData();
                _isAutoSaveModified = false;
            }
            else
            {
                e.Cancel = true;
            }
        }

        protected void SaveData()
        {
            if (InvokeRequired)
            {
                MethodInvoker method = delegate { SaveData(); };
                Invoke(method);
            }
            else
            {
                _dataitemSubject.ItemValue = _subjectTextBox.Text;
                _dataItemEditor.ItemValue = _messageEditor.Text;
            }
        }

        public static void InvokeMessageSent(int messageid)
        {
            SendMessageEventHandler handler = MessageSent;
            if (handler != null)
            {
                handler(null, messageid);
            }
        }

        #region Scintilla Events
        private void MessageEditorCharAdded(object sender, KeyPressEventArgs e)
        {
            switch (e.KeyChar)
            {
                case '\r':
                    int newLine = _messageEditor.Model.LineFromPosition(_messageEditor.CaretPosition);
                    int ep = _messageEditor.Model.EndPositionFromLine(newLine);
                    if (_messageEditor.CaretPosition < ep)
                    {
                        Match m = _quotedLineRx.Match(_messageEditor.Model.GetLine(newLine - 1));

                        if (m.Success)
                        {
                            _messageEditor.Model.InsertText(
                                _messageEditor.Model.PositionFromLine(newLine), 
                                m.Groups["prefix"].Value);            //MLHIDE
                        }
                        else
                        {
                            SmartIndent();
                        }
                    }
                    else
                    {
                        SmartIndent();
                    }

                    break;
                case '[':
                    IEnumerable<string> variants =
                        _knownTags
                            .OrderBy(pair => pair.Key)
                            .Select(pair => (pair.Value ? "[{0}][/{0}]" : "[{0}]").FormatStr(pair.Key)); //MLHIDE

                    _messageEditor.ShowAutocomplete(1, variants);

                    break;
                case ':':
                    _messageEditor.ShowAutocomplete(
                        1, _knownSmiles.OrderBy(smile => smile));
                    break;
            }
        }

        /// <summary>
        /// Handles the Modified event of the MessageEditor control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Rsdn.Scintilla.ModifiedEventArgs"/> instance containing the event data.</param>
        private void MessageEditorModified(object sender, ModifiedEventArgs e)
        {
            if ((e.ModificationTypes & (ModificationTypes.InsertText | ModificationTypes.DeleteText)) > 0)
            {
                OnModified();
            }

            if (((e.ModificationTypes & ModificationTypes.DeleteText) > 0) && (e.LinesAdded == -1))
            {
                int newLine = _messageEditor.Model.LineFromPosition(e.Position);
                if (_messageEditor.Model.PositionFromLine(newLine) != e.Position)
                {
                    int endPositionFromLine = _messageEditor.Model.EndPositionFromLine(newLine);
                    string text = _messageEditor.Model.GetTextRange(e.Position, endPositionFromLine);

                    Match qm = _quotedLineRx.Match(text);

                    if (qm.Success)
                    {
                        Action action =
                            delegate
                            {
                                _messageEditor.Selection.Start = e.Position;
                                _messageEditor.Selection.End = e.Position + qm.Groups["prefix"].Length; //MLHIDE
                                _messageEditor.Selection.Text = string.Empty;
                            };
                        AsyncHelper.Post(action);
                    }
                }
            }
        }

        /// <summary>
        /// Handles the StyleNeeded event of the MessageEditor control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Rsdn.Scintilla.StyleNeededEventArgs"/> instance containing the event data.</param>
        private void MessageEditorStyleNeeded(object sender, StyleNeededEventArgs e)
        {
            int startLine = _messageEditor.Model.LineFromPosition(e.StartPosition);
            int endLine = _messageEditor.Model.LineFromPosition(e.EndPosition);

            for (int i = startLine; i <= endLine; i++)
            {
                string line = _messageEditor.Model.GetLine(i);
                Match match = _quotedLineRx.Match(line);

                int positionFromLine = _messageEditor.Model.PositionFromLine(i);
                _messageEditor.StartStyling(positionFromLine, 31);

                if (match.Success)
                {
                    int styleNum = _level3Style;

                    switch (match.Groups["level"].Value.Length)       //MLHIDE
                    {
                        case 1:
                            styleNum = _level1Style;
                            break;
                        case 2:
                            styleNum = _level2Style;
                            break;
                    }

                    int prefixLength = match.Groups["prefix"].Value.Length; //MLHIDE
                    _messageEditor.SetStyling(prefixLength, _messageEditor.TextStyles[_quotaPrefixStyle]);

                    int length = _messageEditor.Model.GetLineLength(i) - prefixLength;
                    _messageEditor.SetStyling(length, _messageEditor.TextStyles[styleNum]);
                }
                else
                {
                    Match tm = _tagRx.Match(line);
                    Hashtable tags = new Hashtable();

                    while (tm.Success)
                    {
                        tags.Add(tm.Index, tm);
                        tm = tm.NextMatch();
                    }

                    Match smilesMatch = _smileRx.Match(line);
                    Dictionary<int, Match> smiles = new Dictionary<int, Match>();

                    while (smilesMatch.Success)
                    {
                        smiles.Add(smilesMatch.Index, smilesMatch);
                        smilesMatch = smilesMatch.NextMatch();
                    }

                    int j = 0;
                    while (j < line.Length)
                    {
                        if (tags.Contains(j))
                        {
                            Match cm = (Match)tags[j];
                            TextStyle style = _knownTags.ContainsKey(cm.Groups["tag"].Value) //MLHIDE
                                ? _messageEditor.TextStyles[3]
                                : _messageEditor.TextStyles[5];

                            _messageEditor.SetStyling(cm.Length, style);
                            j += cm.Length;
                        }
                        else if (smiles.ContainsKey(j))
                        {
                            Match cm = smiles[j];
                            _messageEditor.SetStyling(cm.Length, _messageEditor.TextStyles[4]);
                            j += cm.Length;
                        }
                        else
                        {
                            _messageEditor.SetStyling(1, _messageEditor.TextStyles[0]);
                            j++;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Handles the AutocompleteSelected event of the MessageEditor control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Rsdn.Scintilla.AutocompleteSelectedEventArgs"/> instance containing the event data.</param>
        private void MessageEditorAutocompleteSelected(object sender, AutocompleteSelectedEventArgs e)
        {
            if (e.Text[0] == '[')
            {
                int middlePos = e.Text.IndexOf(']') + 1;
                SendOrPostCallback sendOrPostCallback =
                    delegate
                    {
                        _messageEditor.CaretPosition = e.WordStart + middlePos;
                    };
                SynchronizationContext.Current.Post(sendOrPostCallback, null);
            }
        }

        /// <summary>
        /// Handles the IsModifiedChanged event of the MessageEditor control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void MessageEditorIsModifiedChanged(object sender, EventArgs e)
        {
            OnIsModifiedChanged();
        }

        /// <summary>
        /// Updates the outbox message.
        /// </summary>
        /// <param name="message">The message.</param>
        private void UpdateOutboxMessage(IOutboxMessage message)
        {
            using (IJanusDataContext db = _serviceManager.CreateDBContext())
            {
                IQueryable<DataModel.IOutboxMessage> outboxMessages = db.OutboxMessages(m => m.ID == message.ID);
                outboxMessages
                    .Set(_ => _.ForumID, m => message.ForumId)
                    .Set(_ => _.Subject, m => message.Subject)
                    .Set(_ => _.Body, m => message.Message)
                    .Set(_ => _.Hold, m => message.Hold)
                    .Update();
            }
        }
        #endregion

        #region Override Events
        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape)
            {
                Close();
            }
            else
            {
                base.OnKeyDown(e);
            }
        }
        #endregion

        #region Shortcuts (Шорткаты)

        [ml_MethodShortcut(Shortcut.CtrlB, 337,"Выделить жирным ([b])", 338,"Поместить тег [b][/b] вокруг выделенной области.")]
        public void InsertBoldTag()
        {
            _serviceManager.TryExecuteCommand(
                "Janus.Forum.MessageEditor.InsertPairTag",            //MLHIDE
                new Dictionary<string, object>
				{
                        {"start", "b"},                               //MLHIDE
                        {"end", "b"},                                 //MLHIDE
                        {"newLine", false}                            //MLHIDE
				});
        }

        [ml_MethodShortcut(Shortcut.CtrlI, 339,"Выделить курсивом ([i])", 340,"Выделить текст курсивом")]
        public void InsertItalicTag()
        {
            _serviceManager.TryExecuteCommand(
                "Janus.Forum.MessageEditor.InsertPairTag",            //MLHIDE
                new Dictionary<string, object>
				{
                        {"start", "i"},                               //MLHIDE
                        {"end", "i"},                                 //MLHIDE
                        {"newLine", false}                            //MLHIDE
				});
        }

        [ml_MethodShortcut(Shortcut.CtrlK, 341,"Зачеркнуть ([s])", 342,"Зачеркнуть текст")]
        public void InsertStrikeTag()
        {
            _serviceManager.TryExecuteCommand(
                "Janus.Forum.MessageEditor.InsertPairTag",            //MLHIDE
                new Dictionary<string, object>
				{
                        {"start", "s"},                               //MLHIDE
                        {"end", "s"},                                 //MLHIDE
                        {"newLine", false}                            //MLHIDE
				});
        }

        [ml_MethodShortcut(Shortcut.CtrlU, 343,"Подчеркнуть ([u])", 344,"Подчеркнуть текст")]
        public void InsertUnderlineTag()
        {
            _serviceManager.TryExecuteCommand(
                "Janus.Forum.MessageEditor.InsertPairTag",            //MLHIDE
                new Dictionary<string, object>
				{
                        {"start", "u"},                               //MLHIDE
                        {"end", "u"},                                 //MLHIDE
                        {"newLine", false}                            //MLHIDE
				});
        }

        [ml_MethodShortcut(Shortcut.CtrlL, 350,"Вставить URL", 350,"Вставить URL")]
        public void InsertUrlTag()
        {
            _serviceManager.TryExecuteCommand(
                "Janus.Forum.MessageEditor.InsertPairTag",            //MLHIDE
                new Dictionary<string, object>
				{
                        {"start", "url="},                            //MLHIDE
                        {"end", "url"},                               //MLHIDE
                        {"newLine", false}                            //MLHIDE
				});
        }

        [ml_MethodShortcut(Shortcut.CtrlQ, 349,"Цитата ([q])", 348,"Отметить выделенный блок как цитату.")]
        public void InsertQTag()
        {
            _serviceManager.TryExecuteCommand(
                "Janus.Forum.MessageEditor.InsertPairTag",            //MLHIDE
                new Dictionary<string, object>
				{
                        {"start", "q"},                               //MLHIDE
                        {"end", "q"},                                 //MLHIDE
                        {"newLine", true}                             //MLHIDE
				});
        }

        [ml_MethodShortcut((Shortcut)(Keys.Alt | Keys.Oemtilde), 347,"Последний использованный язык",346,"Применить тэг последнего использованного языка.")]
        public void InsertLastLanguageTag()
        {
            _serviceManager.TryExecuteCommand(
                "Janus.Forum.MessageEditor.InsertPairTag",            //MLHIDE
                new Dictionary<string, object>
				{
					{ "start", Config.Instance.LastLanguageTag },                    //MLHIDE
					{ "end", Config.Instance.LastLanguageTag },                      //MLHIDE
					{ "newLine", true }                                              //MLHIDE
				});
        }

        [ml_MethodShortcut(Shortcut.Alt1, 351,"Код ([code])", 352,"Отметить выделенный блок как код.")]
        public void InsertCodeTag()
        {
            _serviceManager.TryExecuteCommand(
                "Janus.Forum.MessageEditor.InsertPairTag",            //MLHIDE
                new Dictionary<string, object>
				{
                        {"start", "code"},                            //MLHIDE
                        {"end", "code"},                              //MLHIDE
                        {"newLine", true}                             //MLHIDE
				});
        }

        [ml_MethodShortcut(Shortcut.Alt3,354,"Код C# ([c#])", 353,"Отметить выделенный блок как код на языке C#.")]
        public void InsertCsTag()
        {
            _serviceManager.TryExecuteCommand(
                "Janus.Forum.MessageEditor.InsertPairTag",            //MLHIDE
                new Dictionary<string, object>
				{
                        {"start", "c#"},                              //MLHIDE
                        {"end", "c#"},                                //MLHIDE
                        {"newLine", true}                             //MLHIDE
				});
        }

        [ml_MethodShortcut((Shortcut)(Keys.Alt | Keys.V), 356,"Код VB ([vb])", 355,"Отметить выделенный блок как код на языке VB.")]
        public void InsertVbTag()
        {
            _serviceManager.TryExecuteCommand(
                "Janus.Forum.MessageEditor.InsertPairTag",            //MLHIDE
                new Dictionary<string, object>
				{
                        {"start", "vb"},                              //MLHIDE
                        {"end", "vb"},                                //MLHIDE
                        {"newLine", true}                             //MLHIDE
				});
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Init the forum combo box.
        /// </summary>
        private void InitForumComboBox()
        {
            //ComponentResourceManager resources = new ComponentResourceManager(typeof(MessageForm));

            //_forumsComboBox = new Rsdn.Janus.ForumsBox(_serviceManager);
            _forumsComboBox.Provider = _serviceManager;
            //_headerPanel.Controls.Add(this._forumsComboBox);
            //resources.ApplyResources(this._forumsComboBox, "_forumsComboBox"); ////MLHIDE
            _forumsComboBox.DropDownStyle = ComboBoxStyle.DropDownList;
            _forumsComboBox.FormattingEnabled = true;
            //_forumsComboBox.Name = "_forumsComboBox";                 ////MLHIDE
            //_forumsComboBox.Location = new Point(285, 6);
            //_forumsComboBox.Size = new Size(297, 21);
            _forumsComboBox.ItemHeight = 13;
            _forumsComboBox.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
            _forumsComboBox.MaxDropDownItems = 12;
            _forumsComboBox.TabIndex = 3;
            _forumsComboBox.InitForumsComboBox(_messageInfo.ForumId);
        }

        /// <summary>
        /// Customs the initialize component.
        /// </summary>
        private void CustomInitializeComponent()
        {
            InitForumComboBox();

            _dataItemMessageType.ItemValue = _formMode;
            _dataItemMessageId.ItemValue = _messageInfo.ID;
            _dataItemForumId.ItemValue = _messageInfo.ForumId;
            _dataItemReplyId.ItemValue = _messageInfo.ReplyId;
            _dataItemMessageText.ItemValue = _messageInfo.Message;

            if (_formMode == MessageFormMode.Add || _formMode == MessageFormMode.Reply)
            {
                if (_formMode == MessageFormMode.Add)
                {
                    _subjectTextBox.Text = _messageInfo.Subject;
                    _subjectTextBox.Modified = true;
                    ActiveControl = _subjectTextBox;
                }

                if (_formMode == MessageFormMode.Reply)
                {
                    IFormatMessageHelperService formatMessageService =
                        _provider.GetRequiredService<IFormatMessageHelperService>();

                    _forumsComboBox.Enabled = false;
                    _quoteAnalyzer = new QuoteAnalyzer(_messageInfo.Message);
                    _subjectTextBox.Text = formatMessageService.ReSubj(_messageInfo.Subject);
                    ActiveControl = _messageEditor;
                }


                if (string.IsNullOrEmpty(_editorTextIn))
                {
                    _messageEditor.Text = _messageInfo.Message.Trim();
                }
                else
                {
                    RestoreEditorText();
                }
            }

            if (_formMode == MessageFormMode.Edit)
            {
                _subjectTextBox.Text = _messageInfo.Subject;
                ActiveControl = _messageEditor;

                if (string.IsNullOrEmpty(_editorTextIn))
                {
                    _messageEditor.Text = _messageInfo.Message;
                }
                else
                {
                    RestoreEditorText();
                }
            }

            _fromUserLabel.Text = Config.Instance.Login;

            _dataitemSubject.ItemValue = _subjectTextBox.Text;
            _dataItemEditor.ItemValue = _messageEditor.Text;

            _autoSaver.Start();

            Bounds = Config.Instance.MessageFormBounds.Bounds;
            //SystemInformation si
            Rectangle screenBounds = Screen.GetBounds(this);
            // if window top-left outside the screen hen normalize window
            if (!screenBounds.Contains(Bounds.Left, Bounds.Top))
            {
                screenBounds.Inflate(-100, -100);
                Bounds = screenBounds;
            }
            if (Config.Instance.MessageFormBounds.Maximized)
            {
                WindowState = FormWindowState.Maximized;
            }

            #region Styles
            // style
            Font font = new Font("Courier New", 9F);                  //MLHIDE
            _messageEditor.TextStyles.AddRange(
                new[]
				{
                        new TextStyle(
                            "Default",                                //MLHIDE
                            0, 
                            font, 
                            Color.Black, 
                            Color.White, 
                            false, 
                            CaseMode.Mixed, 
                            true, 
                            false, 
						PredefinedStyle.None),
                        new TextStyle(
                            "Quoted text level 1",                    //MLHIDE
                            1, 
                            font, 
                            Color.FromArgb(0, 92, 0), 
                            Color.White, 
                            false, 
                            CaseMode.Mixed, 
                            true, 
                            false, 
                            PredefinedStyle.None), 
                        new TextStyle(
                            "Quotes prefix",                          //MLHIDE
                            2, 
                            font, 
                            Color.FromArgb(0, 64, 96), 
                            Color.White, 
                            false, 
                            CaseMode.Mixed, 
                            true, 
                            false, 
                            PredefinedStyle.None), 
                        new TextStyle(
                            "Known tags",                             //MLHIDE
                            3, 
                            font, 
                            Color.DarkGreen, 
                            Color.LightGray, 
                            false, 
                            CaseMode.Mixed, 
                            true, 
                            false, 
                            PredefinedStyle.None), 
                        new TextStyle(
                            "Smiles",                                 //MLHIDE
                            4, 
                            font, 
                            Color.DarkBlue, 
                            Color.LightGray, 
                            false, 
                            CaseMode.Mixed, 
                            true, 
                            false, 
                            PredefinedStyle.None), 
                        new TextStyle(
                            "Unknown tag",                            //MLHIDE
                            5, 
                            font, 
                            Color.DarkRed, 
                            Color.LightGray, 
                            false, 
                            CaseMode.Mixed, 
                            true, 
                            false, 
                            PredefinedStyle.None), 
                        new TextStyle(
                            "TagContent",                             //MLHIDE
                            6, 
                            font, 
                            Color.Black, 
                            Color.LemonChiffon, 
                            true, 
                            CaseMode.Mixed, 
                            true, 
                            false, 
                            PredefinedStyle.None), 
                        new TextStyle(
                            "PredefinedBraceLight",                   //MLHIDE
                            34, 
                            font, 
                            Color.Blue, 
                            Color.White, 
                            false, 
                            CaseMode.Mixed, 
                            true, 
                            false, 
                            PredefinedStyle.BraceLight), 
                        new TextStyle(
                            "PredefinedBraceBad",                     //MLHIDE
                            35, 
                            font, 
                            Color.Red, 
                            Color.White, 
                            false, 
                            CaseMode.Mixed, 
                            true, 
                            false, 
                            PredefinedStyle.BraceBad), 
                        new TextStyle(
                            "PredefinedDefault",                      //MLHIDE
                            32, 
                            font, 
                            Color.Black, 
                            Color.White, 
                            false, 
                            CaseMode.Mixed, 
                            true, 
                            false, 
                            PredefinedStyle.Default), 
                        new TextStyle(
                            "Quoted text level 2",                    //MLHIDE
                            10, 
                            font, 
                            Color.Green, 
                            Color.White, 
                            false, 
                            CaseMode.Mixed, 
                            true, 
                            false, 
                            PredefinedStyle.None), 
                        new TextStyle(
                            "Quoted text level 3",                    //MLHIDE
                            11, 
                            font, 
                            Color.FromArgb(0, 160, 0), 
                            Color.White, 
                            false, 
                            CaseMode.Mixed, 
                            true, 
                            false, 
                            PredefinedStyle.None)
				});
            #endregion

            foreach (TextStyle style in _messageEditor.TextStyles.Cast<TextStyle>())
            {
                _messageEditor.SetStyleCharset(style.Number, ScintillaCharset.Russian);
            }

            // _subjectTextBox.Modified = false;
            //_messageEditor.IsModified = true;
            _messageEditor.ClearUndoBuffer(); // Savepoint also set by this call
            OnIsModifiedChanged();
            if (!string.IsNullOrEmpty(_editorTextIn))
            {
                _isModified = true;
            }
            _messageEditor.MultipleSelection = true;
            _messageEditor.AdditionalSelectionTyping = true;

            StyleConfig.StyleChange += OnStyleChanged;
            UpdateStyle();
        }


        /// <summary>
        /// Конструирует отступ с повторением рисунков пробелов предыдущей строки.
        /// </summary>
        private void SmartIndent()
        {
            JanusScintilla editor = _messageEditor;
            TextModel model = _messageEditor.Model;

            int line = model.LineFromPosition(editor.CaretPosition);
            int startPos = model.PositionFromLine(line);

            // Текст отступа
            string indent =
                model
                    .GetLine(line - 1)
                    .TakeWhile(ch => char.IsWhiteSpace(ch) && ch != '\r' && ch != '\n')
                    .Aggregate(new StringBuilder(), (current, ch) => current.Append(ch))
                    .ToString();

            model.InsertText(startPos, indent);
            editor.Selection.Start += indent.Length;
        }

        /// <summary>
        /// Called when [style changed].
        /// </summary>
        /// <param name="s">The s.</param>
        /// <param name="e">The <see cref="Rsdn.Janus.StyleChangeEventArgs"/> instance containing the event data.</param>
        private void OnStyleChanged(object s, StyleChangeEventArgs e)
        {
            UpdateStyle();
            Refresh();
        }

        /// <summary>
        /// Updates the style.
        /// </summary>
        private void UpdateStyle()
        {
            _messageEditor.TextStyles[_level1Style].ForeColor =
                StyleConfig.Instance.Level1QuotaColor;
            _messageEditor.TextStyles[_level2Style].ForeColor =
                StyleConfig.Instance.Level2QuotaColor;
            _messageEditor.TextStyles[_level3Style].ForeColor =
                StyleConfig.Instance.Level3QuotaColor;
            _messageEditor.TextStyles[_quotaPrefixStyle].ForeColor =
                StyleConfig.Instance.QuotaPrefixColor;
            _messageEditor.TabWidth =
                StyleConfig.Instance.WriteMessageTabSize;
        }

        /// <summary>
        /// Saves the message.
        /// </summary>
        /// <param name="closeOnSave">if set to <c>true</c> [close on save].</param>
        private void SaveMessage(bool closeOnSave)
        {
            // subject validation
            if (_subjectTextBox.Text.Trim().Length == 0)
            {
                MessageBox.Show(
                    SR.MessageEditor.SubjectRequired,
                    ApplicationInfo.ApplicationName,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Warning);
                return;
            }

            // fill object from controls
            if (_forumsComboBox.ForumId != -1)
            {
                _messageInfo.ForumId = _forumsComboBox.ForumId;
            }
            _messageInfo.Subject = _subjectTextBox.Text.Trim();
            _messageInfo.Message = _messageEditor.Text.Trim();

            if (!_messageInfo.Hold)
            {
                // overquote validation
                if (_quoteAnalyzer != null && _quoteAnalyzer.IsOverquoted(_messageEditor.Text))
                {
                    // Предлагаем вернуться к редактированию сообщения.
                    if (MessageBox.Show(
                        this,
                        SR.MessageEditor.QuoteWarning,
                        ApplicationInfo.ApplicationName,
                        MessageBoxButtons.YesNo,
                        MessageBoxIcon.Warning) != DialogResult.Yes)
                    {
                        return;
                    }
                }
            }

            if (_formMode == MessageFormMode.Add || _formMode == MessageFormMode.Reply)
            {
                OutboxHelper.AddOutboxMessage(_serviceManager, _messageInfo);

                if (!closeOnSave)
                {
                    using (IJanusDataContext db = _serviceManager.CreateDBContext())
                    {
                        _messageInfo.ID = db.OutboxMessages().Max(m => m.ID);
                    }
                    _formMode = MessageFormMode.Edit;
                }

                _serviceManager.LogInfo(string.Format(SR.MessageEditor.LogNewMsg, _forumsComboBox.Text));
            }
            else
            {
                UpdateOutboxMessage(_messageInfo);
                _serviceManager.LogInfo(string.Format(SR.MessageEditor.LogChangeMsg, _forumsComboBox.Text));
            }

            _messageEditor.SetSavePoint();
            _subjectTextBox.Modified = false;
            OnIsModifiedChanged();

            InvokeMessageSent(_messageInfo.ID);

            if (closeOnSave)
            {
                NativeMethods.PostMessage(Handle, NativeMethods.WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
            }
            _serviceManager.GetRequiredService<IOutboxManager>().NewMessages.Refresh();
            _serviceManager.GetRequiredService<IDraftboxManager>().NewMessages.Refresh();

            // no exception delete stored message
            DeleteAutoSavedVersion();
        }

        /// <summary>
        /// Called when control is modified.
        /// </summary>
        private void OnModified()
        {
            _isAutoSaveModified = _messageEditor.IsModified || _subjectTextBox.Modified;

            InvokeModified();
        }

        /// <summary>
        /// Called when [is modified changed].
        /// </summary>
        private void OnIsModifiedChanged()
        {
            _isModified = _messageEditor.IsModified || _subjectTextBox.Modified;
            _isAutoSaveModified = _isModified;
            InvokeIsModifiedChanged();
        }

        #endregion

        #region Control Events

        /// <summary>
        /// Subjects the text box text changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void SubjectTextBoxTextChanged(object sender, EventArgs e)
        {
            int restTextLength = MaxSubjectLength - _subjectTextBox.Text.Length;
            if (restTextLength < 0)
            {
                _labelSymbolsRest.ForeColor = Color.Red;
                restTextLength = -restTextLength;
            }
            else
            {
                _labelSymbolsRest.ForeColor = Color.Black;
            }
            _labelSymbolsRest.Text = restTextLength.ToString();
            OnModified();
        }

        /// <summary>
        /// Subjects the text box modified changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void SubjectTextBoxModifiedChanged(object sender, EventArgs e)
        {
            OnIsModifiedChanged();
        }

        #endregion


        #region Адаптированный дубликат кода из HtmlPageBuilder.cs
        private const string _templateAlt = "{0}: {1}";               //MLHIDE

        private const string _templateHeaderItem =
            @"<table class='header' width='100%' border='0' cellspacing='0'><tbody>
				<tr>
					<td nowrap>&nbsp;<img class='himg' src='{0}' alt='{1}'>&nbsp;{2}</td>
				</tr>
			</tbody></table>";                                                 //MLHIDE

        public event SmartApp.EventHandler<MessageForm> Modified;

        public event SmartApp.EventHandler<MessageForm> IsModifiedChanged;


        /// <summary>
        /// Invokes the modified event.
        /// </summary>
        public void InvokeModified()
        {
            SmartApp.EventHandler<MessageForm> handler = Modified;
            if (handler != null)
            {
                handler(this);
            }
        }

        public void InvokeIsModifiedChanged()
        {
            SmartApp.EventHandler<MessageForm> handler = IsModifiedChanged;
            if (handler != null)
            {
                handler(this);
            }
        }

        /// <summary>
        /// Formats the subject.
        /// </summary>
        /// <returns></returns>
        [NotNull]
        private string FormatSubject()
        {
            string text = HttpUtility.HtmlEncode(_subjectTextBox.Text);

            return string.Format(
                _templateHeaderItem,
                ImagesHelper.GetMessageImagePath(_serviceManager, true, false, false),
                string.Format(_templateAlt, SR.TGColumnSubject, text),
                text);
        }
        #endregion

        private const string _messagePreviewTemplateResource =
            "Rsdn.Janus.Features.ForumViewer.MessageEditor.PreviewMessage.html"; //MLHIDE


        /// <summary>
        /// Gets a value indicating whether this instance can undo.
        /// </summary>
        /// <value><c>true</c> if this instance can undo; otherwise, <c>false</c>.</value>
        public bool CanUndo
        {
            get { return _messageEditor.CanUndo; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance can redo.
        /// </summary>
        /// <value><c>true</c> if this instance can redo; otherwise, <c>false</c>.</value>
        public bool CanRedo
        {
            get { return _messageEditor.CanRedo; }
        }
        public bool IsModified
        {
            get { return _isModified; }
        }
        #region IPreviewSource Members

        /// <summary>
        /// Получить данные для предварительного просмотра.
        /// </summary>
        /// <returns></returns>
        string IPreviewSource.GetData()
        {
            if (_messagePreviewTemplate == null)
            {
                Assembly assembly = typeof(MessageEditor).Assembly;
                Stream messagePreviewStream = assembly.GetRequiredResourceStream(_messagePreviewTemplateResource);
                using (StreamReader sr = new StreamReader(messagePreviewStream))
                {
                    _messagePreviewTemplate = sr.ReadToEnd();
                }
            }
            ITagLineManager tlm = _serviceManager.GetRequiredService<ITagLineManager>();
            string tl = tlm.GetTagLine(tlm.FindAppropriateTagLine(_forumsComboBox.ForumId));

            IJanusFormatter janusFormatter = _serviceManager.GetFormatter();
            return _messagePreviewTemplate.FormatStr(
                SR.MessageEditor.PreviewTitle,
                FormatSubject(),
                janusFormatter.Format(
                        "{0}\r\n[tagline]{1}[/tagline]".FormatStr(_messageEditor.Model.Text, tl), //MLHIDE
                    true));
        }
        #endregion


        /// <summary>
        /// Sends the message.
        /// </summary>
        public void SendMessage()
        {
            _messageInfo.Hold = false;
            SaveMessage(true);
        }

        /// <summary>
        /// Saves the message.
        /// </summary>
        public void SaveMessage()
        {
            _messageInfo.Hold = true;
            SaveMessage(false);
        }

        /// <summary>
        /// Shows the preview.
        /// </summary>
        public void ShowPreview()
        {
            _serviceManager.OpenUrlInBrowser("janus://outbox-preview/" + _previewSourceNum); //MLHIDE
        }

        /// <summary>
        /// Undoes this instance.
        /// </summary>
        public void Undo()
        {
            _messageEditor.Undo();
        }

        /// <summary>
        /// Redoes this instance.
        /// </summary>
        public void Redo()
        {
            _messageEditor.Redo();
        }

        /// <summary>
        /// Shows the find and replace.
        /// </summary>
        public void ShowFindAndReplace()
        {
            using (FindDialogForm fdf = new FindDialogForm())
            {
                if (fdf.ShowDialog(_messageEditor) != DialogResult.OK)
                {
                    return;
                }
                SearchFlags sf = SearchFlags.None;

                if (fdf.MatchCase)
                {
                    sf |= SearchFlags.MatchCase;
                }
                if (fdf.MatchWholeWord)
                {
                    sf |= SearchFlags.WholeWord;
                }
                if (fdf.MatchWordStart)
                {
                    sf |= SearchFlags.WordStart;
                }
                if (fdf.UseRegex)
                {
                    sf |= SearchFlags.RegExp;
                }
                int start = fdf.SelectionOnly
                    ? _messageEditor.Selection.Start
                    : 0;
                int end = fdf.SelectionOnly
                    ? _messageEditor.Selection.End
                    : _messageEditor.Model.TextLength;

                if (fdf.Reverse)
                {
                    int b = start;
                    start = end;
                    end = b;
                }

                FindTextResult res = _messageEditor.Model.FindText(start, end, fdf.TextToFind, sf);

                if (!res.IsSuccess)
                {
                    MessageBox.Show(
                        SR.MessageEditor.FindNothing,
                        ApplicationInfo.ApplicationName,
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Information);
                }
                else
                {
                    _messageEditor.Selection.Start = res.FindStart;
                    _messageEditor.Selection.End = res.FindEnd;
                }
            }
        }

        /// <summary>
        /// Surrounds the text.
        /// </summary>
        /// <param name="start">The start.</param>
        /// <param name="end">The end.</param>
        /// <param name="newLine">if set to <c>true</c> [new line].</param>
        public void SurroundText(string start, string end, bool newLine)
        {
            string newLineLocal = string.Empty;
            if (newLine)
            {
                newLineLocal = Environment.NewLine;
            }
            _messageEditor.Selection.Text = "{0}{1}{2}{1}{3}".FormatStr( //MLHIDE
                start,
                newLineLocal,
                _messageEditor.Selection.Text,
                end);
        }

        /// <summary>
        /// Inserts the text.
        /// </summary>
        /// <param name="text">The text.</param>
        public void InsertText(string text)
        {
            _messageEditor.Selection.Text = text;
        }

        /// <summary>
        /// Deletes the auto saved version.
        /// </summary>
        private void DeleteAutoSavedVersion()
        {
            _autoSaver.Delete();
        }

        private void RestoreEditorText()
        {
            _messageEditor.Text = _editorTextIn;
            string title = Text;
            title += ml.ml_string(345, " - восстановленная версия");
            Text = title;
            //_isModified = true;
            //InvokeIsModifiedChanged();
        }
    }
}